/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.datatheorem.ohos.trustkit.reporting;


import com.datatheorem.ohos.trustkit.asyntaskutils.TaskDispatcherCallBack;
import com.datatheorem.ohos.trustkit.utils.TrustKitLog;
import com.datatheorem.ohos.trustkit.config.DomainPinningPolicy;
import com.datatheorem.ohos.trustkit.ohad.Base64;
import com.datatheorem.ohos.trustkit.pinning.PinningValidationResult;

import ohos.aafwk.content.Intent;
import ohos.aafwk.content.Operation;
import ohos.app.Context;
import ohos.event.commonevent.CommonEventData;
import ohos.event.commonevent.CommonEventManager;
import ohos.event.commonevent.CommonEventPublishInfo;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.rpc.RemoteException;

import java.net.URL;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.sql.Date;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;


public class BackgroundReporter {
    // 定义日志标签
    private static final HiLogLabel DEBUG_TAG = new HiLogLabel(HiLog.LOG_APP, 0x00201, "TrustKit");

    public static final String REPORT_VALIDATION_EVENT = "com.datatheorem.ohos.trustkit.reporting.BackgroundReporter:REPORT_VALIDATION_EVENT";

    // App meta-data to be sent with the reports
    private final String appPackageName;
    private final String appVersion;
    private final String appVendorId;
    private final Context context;

    public BackgroundReporter( Context context,  String appPackageName,  String appVersion,
                               String appVendorId) {
        this.context = context;
        this.appPackageName = appPackageName;
        this.appVersion = appVersion;
        this.appVendorId = appVendorId;

    }

    private static String certificateToPem(X509Certificate certificate) {
        byte[] certificateData;
        try {
            certificateData = certificate.getEncoded();
        } catch (CertificateEncodingException e) {
            throw new IllegalStateException("Should never happen - certificate was previously " +
                "parsed by the system");
        }

        // Create the PEM string
        String certificateAsPem = "-----BEGIN CERTIFICATE-----\n";
        certificateAsPem += Base64.encodeToString(certificateData, Base64.DEFAULT);
        certificateAsPem += "-----END CERTIFICATE-----\n";
        return certificateAsPem;
    }

    /**
     * Try to send a pin validation failure report to the reporting servers configured for the
     * hostname that triggered the failure.
     *
     * @param serverHostname
     * @param serverPort
     * @param servedCertificateChain
     * @param validatedCertificateChain
     * @param serverConfig
     * @param validationResult
     */
    public void pinValidationFailed( String serverHostname,
                                     Integer serverPort,
                                     List<X509Certificate> servedCertificateChain,
                                     List<X509Certificate> validatedCertificateChain,
                                     DomainPinningPolicy serverConfig,
                                     PinningValidationResult validationResult) {

        TrustKitLog.i("Generating pin failure report for " + serverHostname);

        // Convert the certificates to PEM strings
        ArrayList<String> validatedCertificateChainAsPem = new ArrayList<>();
        for (X509Certificate certificate : validatedCertificateChain) {
            validatedCertificateChainAsPem.add(certificateToPem(certificate));
        }
        ArrayList<String> servedCertificateChainAsPem = new ArrayList<>();
        for (X509Certificate certificate : servedCertificateChain) {
            servedCertificateChainAsPem.add(certificateToPem(certificate));
        }

        // Generate the corresponding pin failure report
        PinningFailureReport report = new PinningFailureReport(appPackageName, appVersion,
            appVendorId, serverHostname, serverPort,
            serverConfig.getHostname(), serverConfig.shouldIncludeSubdomains(),
            serverConfig.shouldEnforcePinning(), servedCertificateChainAsPem,
            validatedCertificateChainAsPem, new Date(System.currentTimeMillis()),
            serverConfig.getPublicKeyPins(), validationResult);

        // If a similar report hasn't been sent recently, send it now
        if (!(ReportRateLimiter.shouldRateLimit(report))) {
            sendReport(report, serverConfig.getReportUris());
            broadcastReport(report);
        } else {
            TrustKitLog.i("Report for " + serverHostname + " was not sent due to rate-limiting");
        }
    }

    protected void sendReport( PinningFailureReport report,
                               Set<URL> reportUriSet) {
        // Prepare the AsyncTask's arguments
        ArrayList<Object> taskParameters = new ArrayList<>();
        taskParameters.add(report);
        taskParameters.addAll(reportUriSet);
        // Call the task
        new BackgroundReporterTask(context, new TaskDispatcherCallBack() {
            @Override
            public void onTaskDispatcherBack() {
                taskParameters.toArray();
            }

            @Override
            public void onTaskDispatcherDoLastBack() {

            }
        }).start(taskParameters);
    }

    //TODO 广播无法设置拦截器
    protected void broadcastReport( PinningFailureReport report) {

        Intent intent = new Intent();
        Operation operation = new Intent.OperationBuilder()
            .withAction(REPORT_VALIDATION_EVENT)
            .build();
        intent.setOperation(operation);
        CommonEventData eventData = new CommonEventData(intent);
        CommonEventPublishInfo publishInfo = new CommonEventPublishInfo();
        String[] permissions = {"com.example.MyApplication.permission"};
        publishInfo.setSubscriberPermissions(permissions); // 设置权限
        try {
            CommonEventManager.publishCommonEvent(eventData, publishInfo);
        } catch (RemoteException e) {
            HiLog.error(DEBUG_TAG, "Exception occurred during publishCommonEvent invocation.");
        }
    }
}

